const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'

const resolvePromise = (promise, x, resolve, reject) => {
	if (promise === x) {
		throw TypeError('TypeError')
	}
	let called = false
	if (x != null && ['function', 'object'].includes(typeof x)) {
		try {
			const then = x.then
			if (typeof then === 'function') {
				then.call(
					x,
					(y) => {
						if (called) return
						called = true
						resolvePromise(promise, y, resolve, reject)
					},
					(r) => {
						if (called) return
						called = true
						reject(r)
					}
				)
			} else {
				resolve(x)
			}
		} catch (e) {
			if (called) return
			called = true
			reject(e)
		}
	} else {
		resolve(x)
	}
}

class Promise {
	constructor(executor) {
		this.status = PENDING
		this.value = undefined
		this.reason = undefined
		this.onFulfilledCallbacks = []
		this.onRejectedCallbacks = []

		const resolve = (value) => {
			if (this.status === PENDING) {
				this.status = FULFILLED
				this.value = value
				this.onFulfilledCallbacks.forEach((fn) => fn())
			}
		}

		const reject = (reason) => {
			if (this.status === PENDING) {
				this.status = REJECTED
				this.reason = reason
				this.onRejectedCallbacks.forEach((fn) => fn())
			}
		}

		executor(resolve, reject)
	}

	then = (onFulfilled, onRejected) => {
		onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value
		onRejected =
			typeof onRejected === 'function'
				? onRejected
				: (err) => {
						throw err
				  }

		const promise2 = new Promise((resolve, reject) => {
			if (this.status === FULFILLED) {
				setTimeout(() => {
					try {
						let x = onFulfilled(this.value)
						resolvePromise(promise2, x, resolve, reject)
					} catch (e) {
						reject(e)
					}
				})
			}

			if (this.status === REJECTED) {
				setTimeout(() => {
					try {
						let x = onRejected(this.reason)
						resolvePromise(promise2, x, resolve, reject)
					} catch (e) {
						reject(e)
					}
				})
			}

			if (this.status === PENDING) {
				this.onFulfilledCallbacks.push(() => {
					setTimeout(() => {
						try {
							let x = onFulfilled(this.value)
							resolvePromise(promise2, x, resolve, reject)
						} catch (e) {
							reject(e)
						}
					})
				})

				this.onRejectedCallbacks.push(() => {
					setTimeout(() => {
						try {
							let x = onRejected(this.reason)
							resolvePromise(promise2, x, resolve, reject)
						} catch (e) {
							reject(e)
						}
					})
				})
			}
		})

		return promise2
	}

	catch = (errCallback) => {
		return this.then(null, errCallback)
	}

	finally = (fn) => {
		return this.then(fn, fn)
	}

	all = (arr) => {
		let aResult = []
		return new Promise((resolve, reject) => {
			let i = 0
			const next = () => {
				arr[i].then((res) => {
					aResult.push(res)
					i++
					if (~~i === ~~arr.length) {
						resolve(aResult)
					} else {
						next()
					}
				})
			}
			next()
		})
	}

	static resolve = (value) => {
		return new Promise((resolve, reject) => {
			resolve(value)
		})
	}

	static reject = (reason) => {
		return new Promise((resolve, reject) => {
			reject(reason)
		})
	}
}

const p = new Promise((resolve, reject) => {
	resolve('哈哈哈')
})

// p.then(data => {
//   console.log(data, 111)
//   return data
// }).then(data => {
//   console.log(data, 222)
//   throw new Error("呵呵")
// }).catch(err => {
//   console.log(err, 333)
//   return "嘿嘿"
// }).finally(res => {
//   console.log(res, 444)
// })

const p1 = new Promise((resolve, reject) => {
	resolve('哈哈1 ---')
})
p1.then((res) => console.log(res))

const p2 = new Promise((resolve, reject) => {
	resolve('哈哈2 ---')
})
p2.then((res) => console.log(res))

const p3 = new Promise((resolve, reject) => {
	reject('哈哈3 ---')
})
p3.then((res) => console.log(res)).catch((err) => console.log(err))

const p4 = new Promise((resolve, reject) => {
	resolve('22222')
})
p4.all([p1, p2, p3])

// npm i promises-aplus-tests
// npx promises-aplus-tests promise.js

// Promise.defer = Promise.deferred = function () {
//      let dfd = {}
//      dfd.promise = new Promise((resolve, reject) => {
//         dfd.resolve = resolve
//         dfd.reject = reject
//     })
//     return dfd
//  }
//
// module.exports = Promise;
